﻿// Created by amoylel on 10/04/2018.
// Copyright (c) 2018 amoylel All rights reserved.

#ifndef AMO_BASIC_JSON_0E4D4BB8_2C4C_4DAC_9764_11606B181646_HPP__
#define AMO_BASIC_JSON_0E4D4BB8_2C4C_4DAC_9764_11606B181646_HPP__



#ifndef RAPIDJSON_HAS_STDSTRING
#define RAPIDJSON_HAS_STDSTRING 1
#endif

#include <vector>
#include <string>
#include <iosfwd>
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <utility>
#include <cstdint>




#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
#include <rapidjson/filereadstream.h>
#include <rapidjson/filewritestream.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/ostreamwrapper.h>
#include <rapidjson/istreamwrapper.h>



#include <amo/platforms.hpp>
#include <amo/uuid.hpp>
#include <amo/date_time.hpp>
#include <amo/nil.hpp>

#include <amo/string/coding_type.hpp>
#include <amo/impl/json_value.hpp>

#ifndef AMO_NO_BOOST
#include <amo/string/string_utils.hpp>
#endif



#ifdef AMO_HAS_NLOHMANN
#include <nlohmann/json.hpp>
typedef nlohmann::json njson;

#endif // AMO_HAS_NLOHMANN


namespace amo {

    template<typename CodingType>
    class basic_json : public json_value {
    public:
        typedef std::string string_type;
        typedef CodingType coding_type;
    public:
        static   StringCodingType get_coding_type() {
            return CodingType::coding_type;
        }
        
        static bool is_utf8() {
            return CodingType::coding_type == StringCodingType::CODING_TYPE_UTF8;
        }
    public:
        typedef rapidjson::Value::MemberIterator  iterator;
        typedef rapidjson::Value::MemberIterator assoc_iterator;
        typedef const rapidjson::Value::ConstMemberIterator const_iterator;
        typedef rapidjson::Document::AllocatorType allocator_type;
        typedef rapidjson::Value::ValueIterator	value_iterator;
        typedef rapidjson::Value::ConstValueIterator	const_value_iterator;
    public:
        static basic_json from_file(const std::string& file) {
            try {
                basic_json json;
                json.parse(file);
                return json;
                
            } catch (const std::exception& e) {
                std::cerr << e.what() << std::endl;
                basic_json retval = basic_json();
                retval.set_valid(false);
                retval.set_error_msg(e.what());
                return retval;
            }
            
        }
        
        static basic_json from_file(const std::istream& stream) {
            try {
                basic_json json;
                json.parse(stream);
                return json;
                
            } catch (const std::exception& e) {
                std::cerr << e.what() << std::endl;
                basic_json retval = basic_json();
                retval.set_valid(false);
                retval.set_error_msg(e.what());
                return retval;
            }
            
        }
    public:
    
        basic_json(bool is_boject = true) : json_value(&doc) {
            m_valid = true;
            ignore_case = false;
            
            if (is_boject) {
                doc.SetObject();
            } else {
                set_array();
            }
        }
        
        basic_json(const std::string& str_json) : json_value(&doc) {
            m_valid = true;
            ignore_case = false;
            
            if (str_json.size() > 1000) {
                std::stringstream stream;
                stream << str_json;
                parse(stream);
            } else {
                doc.Parse<0>(str_json.c_str());
                checkParseError();
            }
            
        }
        
        basic_json(const char* str_json) : json_value(&doc) {
            m_valid = true;
            ignore_case = false;
            
            if (str_json == NULL) {
                return;
            }
            
            if (strlen(str_json) > 1000) {
                std::stringstream stream;
                stream << str_json;
                parse(stream);
            } else {
                doc.Parse<0>(str_json);
                checkParseError();
            }
        }
        
#ifdef AMO_HAS_NLOHMANN
        basic_json(
            nlohmann::json::initializer_list_t init,
            bool type_deduction = true,
            nlohmann::json::value_t manual_type = nlohmann::json::value_t::array) :
            json_value(&doc) {
            
            std::string str_json = nlohmann::json(init, type_deduction,
                                                  manual_type).dump();
            m_valid = true;
            ignore_case = false;
            
            if (str_json.size() > 1000) {
                std::stringstream stream;
                stream << str_json;
                parse(stream);
            } else {
                doc.Parse<0>(str_json.c_str());
                
                checkParseError();
            }
        }
#endif
        
        basic_json(const basic_json& rhs) : json_value(&doc) {
        
            doc.CopyFrom(rhs.doc, get_allocator());
            m_valid = rhs.m_valid;
            m_error_msg = rhs.m_error_msg;
            ignore_case = rhs.ignore_case;
            checkParseError();
        }
        
        basic_json(std::istream& stream) : json_value(&doc) {
            try {
                ignore_case = false;
                rapidjson::IStreamWrapper isw(stream);
                doc.ParseStream(isw);
                m_valid = true;
                checkParseError();
            } catch (const std::exception& e) {
                m_error_msg = e.what();
                m_valid = false;
                return;
            }
        }
        
        basic_json(const rapidjson::Document& document) : json_value(&doc) {
        
            doc.CopyFrom(document, get_allocator());
            m_valid = true;
            ignore_case = false;
            checkParseError();
        };
        
        basic_json(const rapidjson::Value& val) : json_value(&doc) {
        
            doc.CopyFrom(val, get_allocator());
            m_valid = true;
            ignore_case = false;
            checkParseError();
        }
        
        basic_json(const json_value& rhs) : json_value(&doc) {
            m_valid = rhs.m_valid;
            ignore_case = rhs.ignore_case;
            
            if (!rhs.isNull()) {
                doc.CopyFrom(*rhs.m_value, get_allocator());
            } else {
                doc.SetObject();
            }
            
            checkParseError();
        }
        
        basic_json& operator =(const basic_json & rhs) {
        
            doc.CopyFrom(rhs.doc, get_allocator());
            m_valid = rhs.m_valid;
            ignore_case = rhs.ignore_case;
            m_error_msg = rhs.m_error_msg;
            return *this;
        }
        
        basic_json& join(const basic_json& val) {
            basic_json other = val;
            
            for (iterator iter = other.begin(); iter != other.end(); ++iter) {
                std::string name = iter->name.GetString();
                iterator it = find_member(name);
                
                if (it != this->end()) {
                    it->value.CopyFrom(iter->value, get_allocator());
                    
                } else {
                
                    rapidjson::Value node;
                    node.CopyFrom(iter->value, get_allocator());
                    
                    rapidjson::Value key;
                    key.CopyFrom(iter->name, get_allocator());
                    
                    doc.AddMember(key, node, get_allocator());
                }
            }
            
            
            return *this;
        }
        
#ifndef AMO_NO_BOOST
        
        bool parse(const std::string & fileName) {
            try {
#ifdef OS_WIN
                std::ifstream ifs(amo::string_utils::string_to_wide(fileName,
                                  get_coding_type()));
                rapidjson::IStreamWrapper isw(ifs);
                doc.ParseStream(isw);
#else
                std::ifstream ifs(amo::string_utils::string_to_ansi(fileName,
                                  get_coding_type()));
                
                rapidjson::IStreamWrapper isw(ifs);
                doc.ParseStream(isw);
#endif
                
                
                checkParseError();
            } catch (const std::exception& e) {
                m_valid = false;
                m_error_msg = e.what();
                return false;
            }
            
            return true;
        }
#endif
        bool parse(std::istream& stream) {
            try {
                ignore_case = false;
                rapidjson::IStreamWrapper isw(stream);
                doc.ParseStream(isw);
                m_valid = true;
                checkParseError();
            } catch (const std::exception& e) {
                m_valid = false;
                m_error_msg = e.what();
                return false;
            }
            
            return true;
        }
        
#ifndef AMO_NO_BOOST
        
        basic_json to_utf8() const {
            return basic_json(amo::string_utils::string_to_utf8(this->to_string(),
                              get_coding_type()));
        }
        
        basic_json to_ansi() const {
            return basic_json(amo::string_utils::string_to_ansi(this->to_string(),
                              get_coding_type()));
        }
#endif
        
        allocator_type& get_allocator() {
            return doc.GetAllocator();
        }
        
        allocator_type* get_allocator_ptr() {
            return &doc.GetAllocator();
        }
        
        rapidjson::Document& get_document() {
            return doc;
        }
        
        rapidjson::Document get_document_copy() {
            rapidjson::Value retval;
            retval.CopyFrom(doc, get_allocator());
            return retval;
        }
        
        /* rapidjson::Value get_value_copy(const std::string& key) {
             rapidjson::Value retval;
             auto iter = find_member(key);
        
             if (iter == end()) {
                 return retval;
             }
        
             retval.CopyFrom(iter->value, get_allocator());
             return retval;
         }
         */
        
        std::vector<std::pair<std::string, basic_json> > values() {
            std::vector<std::pair<std::string, basic_json> > vec;
            
            for (auto iter = begin(); iter != end(); ++iter) {
                vec.push_back(std::pair<std::string, basic_json>(
                                  iter->name.GetString(), basic_json(iter->value)));
                                  
            }
            
            return vec;
        }
        
        
        /**
        * @fn	std::vector<basic_json> to_array()
        *
        * @brief	将JSON 转换为JSON数组.
        *
        * @return	This object as a std::vector&lt;basic_json&gt;
        */
        
        std::vector<basic_json> to_array() const {
            std::vector<basic_json> vec;
            
            if (!is_array()) {
                return vec;
            }
            
            const_value_iterator iter = doc.Begin();
            
            for (const_value_iterator iter = doc.Begin(); iter != doc.End(); ++iter) {
                std::stringstream ss;
                rapidjson::OStreamWrapper osw(ss);
                rapidjson::Writer< rapidjson::OStreamWrapper> writer(osw);
                iter->Accept(writer);
                //std::string ss.str();
                /*  OutputDebugStringA(ss.str().c_str());
                OutputDebugStringA("\n");*/
                vec.push_back((ss));
            }
            
            return vec;
        }
        
        /**
        * @fn	int to_array(std::function<void(basic_json&)> fn)
        *
        * @brief	将JSON 转换为JSON数组.
        *
        * @param [in,out]	fn	The function.
        *
        * @return	fn as an int.
        */
        
        int to_array(std::function<void(basic_json&)> fn) {
        
            int nCount = 0;
            
            if (!is_array()) {
                return nCount;
            }
            
            value_iterator iter = doc.Begin();
            
            for (value_iterator iter = doc.Begin(); iter != doc.End(); ++iter) {
                std::stringstream ss;
                rapidjson::OStreamWrapper osw(ss);
                rapidjson::Writer< rapidjson::OStreamWrapper> writer(osw);
                iter->Accept(writer);
                
                ++nCount;
                basic_json val(ss);
                
                if (fn) {
                    fn(val);
                }
                
            }
            
            return nCount;
        }
        
        
        void to_file(const std::fstream& ofs) const {
            rapidjson::OStreamWrapper osw(ofs);
            rapidjson::Writer<rapidjson::OStreamWrapper> writer(osw);
            doc.Accept(writer);
        }
        
#ifndef AMO_NO_BOOST
        void to_file(const std::string& filename) const {
        
#ifdef OS_WIN
        
            std::fstream ofs(amo::string_utils::string_to_wide(filename,
                             get_coding_type()), std::ios::out | std::ios::binary);
            rapidjson::OStreamWrapper osw(ofs);
            rapidjson::Writer<rapidjson::OStreamWrapper> writer(osw);
            doc.Accept(writer);
            
#else
            std::fstream ofs(amo::string_utils::string_to_ansi(filename,
                             get_coding_type()), std::ios::out | std::ios::binary);
            rapidjson::OStreamWrapper osw(ofs);
            rapidjson::Writer<rapidjson::OStreamWrapper> writer(osw);
            doc.Accept(writer);
#endif
            
            return;
        }
#endif
        
    private:
        void checkParseError() {
            if (doc.HasParseError()) {
                m_error_msg = " parse error ("
                              + std::to_string(doc.GetParseError()) + ")"
                              + " position:[" + std::to_string(doc.GetErrorOffset()) + "]";
                this->set_valid(false);
            }
        }
        
    private:
    
        rapidjson::Document doc;
        
        
    };
    
    
}


#endif // AMO_BASIC_JSON_0E4D4BB8_2C4C_4DAC_9764_11606B181646_HPP__

